import socket
import queue
from concurrent.futures import ThreadPoolExecutor
"""
1、多人聊天室
2、支持多人连接
3、可以广播消息给所有人
"""
class Server:
    def __init__(self, abbr_ip = '0.0.0.0', port = 30001):
        """
        初始化函数
        :param abbr_ip:
        :param port:
        """
        #初始化tcp链接
        self.ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.abbr_ip = abbr_ip
        self.port = port
        self.abbr = (self.abbr_ip,self.port)
        #绑定ip，端口
        self.ss.bind(self.abbr)
        #进行监听有否链接进来（堵塞）
        self.ss.listen(10)
        #初始化一个装msg的queue
        self.stats = queue.Queue()
        #初始化一个用来装链接进来的链接
        self.conn_list = []
        #初始化一个线程池
        self.pools = ThreadPoolExecutor(10)

        self.conn2pool = dict()
    def start_acp(self):
        """
        等待接收链接
        """
        #因为要连续接收链接，所以加个循环
        print('start server')
        # self.pools.submit(self.broadcast_msg)
        while True:
            conn, address = self.ss.accept()
            self.multiple_conns(conn)
            #链接打通后就该接收消息并发送消息
            #这里需要用多线程来做，因为这里也是个循环，会堵塞，用线程，就能跳过这个阻塞
            self.pools.submit(self.recv,conn,address)
            self.pools.submit(self.broadcast_msg)


    def recv(self,conn,addr):
        """
        接收消息

        :return:
        """
        #因为要一直接收消息所以加循环
        while True:
            msg = conn.recv(1024)
            if (not msg) or (len(msg) == 0):
                #判断：若消息为空（长度为0），或没有消息，者断开链接
                self.move_conn(conn,addr)
            self.collect_msg(self.bytes2str(msg),addr)
            # self.send(msg,conn,addr)


    def send(self, msg, conn, addr):
        """
        将接收到的客户的消息，返回
        :return:
        """

        conn.send(('ip为{}的用户:{}'.format(addr,self.bytes2str(msg))).encode('utf8'))


    def multiple_conns(self,conn):
        """
        1、将链接进来的用户收集到list中
        :return:
        """
        self.conn_list.append(conn)

    def broadcast_msg(self):
        """
        广播消息：有消息进来就要广播，因此加循环
        :return:
        """
        while True:
            msg, addr = self.stats.get()
            print(self.bytes2str(msg))
            for i in self.conn_list:
                try:
                    self.send(self.str2bytes(msg),i,addr)
                except Exception:
                    self.move_conn(i, addr)
    def collect_msg(self, msg, addr):
        """
        收集消息，只有收集到消息才能广播出去，广播消息是服务端主动开辟一条线发送消息
        :return:
        """
        self.stats.put((self.bytes2str(msg),addr))

    def move_conn(self,conn,addr):
        """
        若用户断开链接则移除,并广播该用户退出聊天室
        :return:
        """
        conn.close()
        self.conn_list.remove(conn)
        finally_msg = '退出聊天室'.encode('utf8')
        self.stats.put((finally_msg,addr))
        pool = self.conn2pool[conn]
        pool.shutdown()

    def str2bytes(self, msg):
        """
        字符串转二进制
        :param msg:
        :return:
        """
        try:
            return msg.encode()
        except Exception:
            return msg

    def bytes2str(self, msg):
        """
        二进制转字符串
        :param msg:
        :return:
        """
        try:
            return msg.decode()
        except Exception:
            return msg



if __name__ == '__main__':
    server = Server()
    server.start_acp()